////////////////////////////////////////////////////////////////////////////////
// This file is part of the Mist compiler.                                    //
//                                                                            //
// Copyright (C) 2009, 2010 The Mist project                                  //
//                                                                            //
// For a list of contributors, see the COMMITTERS file.                       //
//                                                                            //
// The Mist compiler is free software: you can redistribute it and/or modify  //
// it under the terms of the GNU General Public License as published by       //
// the Free Software Foundation, either version 3 of the License, or          //
// (at your option) any later version.                                        //
//                                                                            //
// The Mist compiler is distributed in the hope that it will be useful,       //
// but WITHOUT ANY WARRANTY; without even the implied warranty of             //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              //
// GNU General Public License for more details.                               //
//                                                                            //
// You should have received a copy of the GNU General Public License          //
// in the COPYING file. If not, see <http://www.gnu.org/licenses/>.           //
////////////////////////////////////////////////////////////////////////////////

///
/// \file
/// Contains the implementation and private subclasses of the Message class.
///

#include "message.hpp"

////////////////////////////////////////////////////////////////////////////////
// Includes                                                                   //
////////////////////////////////////////////////////////////////////////////////

#include <boost/tuple/tuple.hpp>
#include <boost/tuple/tuple_comparison.hpp>
using boost::make_tuple;

#include <boost/format.hpp>
using boost::format;

#include <boost/foreach.hpp>
#define foreach BOOST_FOREACH

#include "common/assert.hpp"
#include "messageinfo.hpp"
#include "codestream.hpp"

////////////////////////////////////////////////////////////////////////////////
// Concrete Message Classes                                                   //
////////////////////////////////////////////////////////////////////////////////

/// A message with no location in the code.
///
class NoLocationMessage : public Message {
	public:
		
		string message() const;
		
		bool can_merge_with(sptr<Message> other) const;
		
		void merge_with(sptr<Message> other);
		
		bool operator<(const Message& other) const;
		
		ostream& send_to_stream(ostream& out) const;
		
		vector<string> params;
};

/// A message with a single location in the code.
///
class SingleLocationMessage : public Message {
	public:
		
		SingleLocationMessage(Location loc) : location(loc) {}
		
		string message() const;
		
		bool can_merge_with(sptr<Message> other) const;
		
		void merge_with(sptr<Message> other);
		
		bool operator<(const Message& other) const;
		
		ostream& send_to_stream(ostream& out) const;
		
		vector<string> params;
		Location location;
};

/// A message that is only valid with more than one location in the code.
///
class MultipleLocationMessage : public Message {
	public:
		
		string message() const;
		
		bool can_merge_with(sptr<Message> other) const;
		
		void merge_with(sptr<Message> other);
		
		bool operator<(const Message& other) const;
		
		ostream& send_to_stream(ostream& out) const;
		
		vector<string> params;
		set<Location> locations;
};

/// A message that may be merged out of multiple adjacent code locations.
///
class CompoundLocationMessage : public Message {
	public:
		
		CompoundLocationMessage(Location loc) : location(loc) {}
		
		string message() const;
		
		bool can_merge_with(sptr<Message> other) const;
		
		void merge_with(sptr<Message> other);
		
		bool operator<(const Message& other) const;
		
		ostream& send_to_stream(ostream& out) const;
		
		vector<string> params;
		Location location;
};

////////////////////////////////////////////////////////////////////////////////
// Function Implementations                                                   //
////////////////////////////////////////////////////////////////////////////////

sptr<Message> Message::construct(const string& code) {
	assert(the(MessageInfo).type(code) == NoLocation);
	
	sptr<NoLocationMessage> result(new NoLocationMessage);
	result->_code = code;
	
	return result;
}

sptr<Message> Message::construct(const string& code, const string& param1) {
	assert(the(MessageInfo).type(code) == NoLocation);
	
	sptr<NoLocationMessage> result(new NoLocationMessage);
	result->_code = code;
	result->params.push_back(param1);
	
	return result;
}

sptr<Message> Message::construct(const string& code, const string& param1, const string& param2) {
	assert(the(MessageInfo).type(code) == NoLocation);
	
	sptr<NoLocationMessage> result(new NoLocationMessage);
	result->_code = code;
	result->params.push_back(param1);
	result->params.push_back(param2);
	
	return result;
}

sptr<Message> Message::construct(const string& code, const string& param1, const string& param2, const string& param3) {
	assert(the(MessageInfo).type(code) == NoLocation);
	
	sptr<NoLocationMessage> result(new NoLocationMessage);
	result->_code = code;
	result->params.push_back(param1);
	result->params.push_back(param2);
	result->params.push_back(param3);
	
	return result;
}

sptr<Message> Message::construct(const string& code, const Location& location) {
	assert(the(MessageInfo).type(code) == SingleLocation);
	
	sptr<SingleLocationMessage> result(new SingleLocationMessage(location));
	result->_code = code;
	
	return result;
}

sptr<Message> Message::construct(const string& code, const Location& location, const string& param1) {
	assert(the(MessageInfo).type(code) != NoLocation);
	
	if (the(MessageInfo).type(code) == SingleLocation) {
		sptr<SingleLocationMessage> result(new SingleLocationMessage(location));
		result->_code = code;
		result->params.push_back(param1);
		return result;
	}
	
	if (the(MessageInfo).type(code) == MultipleLocation) {
		sptr<MultipleLocationMessage> result(new MultipleLocationMessage);
		result->_code = code;
		result->params.push_back(param1);
		result->locations.insert(location);
		return result;
	}
	
	assert(the(MessageInfo).type(code) == CompoundLocation);
	sptr<CompoundLocationMessage> result(new CompoundLocationMessage(location));
	result->_code = code;
	result->params.push_back(param1);
	return result;
}

sptr<Message> Message::construct(const string& code, const Location& location, const string& param1, const string& param2) {
	assert(the(MessageInfo).type(code) != NoLocation);
	
	if (the(MessageInfo).type(code) == SingleLocation) {
		sptr<SingleLocationMessage> result(new SingleLocationMessage(location));
		result->_code = code;
		result->params.push_back(param1);
		result->params.push_back(param2);
		return result;
	}
	
	if (the(MessageInfo).type(code) == MultipleLocation) {
		sptr<MultipleLocationMessage> result(new MultipleLocationMessage);
		result->_code = code;
		result->params.push_back(param1);
		result->params.push_back(param2);
		result->locations.insert(location);
		return result;
	}
	
	assert(the(MessageInfo).type(code) == CompoundLocation);
	sptr<CompoundLocationMessage> result(new CompoundLocationMessage(location));
	result->_code = code;
	result->params.push_back(param1);
	result->params.push_back(param2);
	return result;
}

sptr<Message> Message::construct(const string& code, const Location& location, const string& param1, const string& param2, const string& param3) {
	assert(the(MessageInfo).type(code) != NoLocation);
	
	if (the(MessageInfo).type(code) == SingleLocation) {
		sptr<SingleLocationMessage> result(new SingleLocationMessage(location));
		result->_code = code;
		result->params.push_back(param1);
		result->params.push_back(param2);
		result->params.push_back(param3);
		return result;
	}
	
	if (the(MessageInfo).type(code) == MultipleLocation) {
		sptr<MultipleLocationMessage> result(new MultipleLocationMessage);
		result->_code = code;
		result->params.push_back(param1);
		result->params.push_back(param2);
		result->params.push_back(param3);
		result->locations.insert(location);
		return result;
	}
	
	assert(the(MessageInfo).type(code) == CompoundLocation);
	sptr<CompoundLocationMessage> result(new CompoundLocationMessage(location));
	result->_code = code;
	result->params.push_back(param1);
	result->params.push_back(param2);
	result->params.push_back(param3);
	return result;
}

string Message::code() const {
	return _code;
}

////////////////////////////////////////////////////////////////////////////////

string NoLocationMessage::message() const {
	format result(the(MessageInfo).message(code()));
	foreach (string p, params)
		result % p;
	return result.str();
}

bool NoLocationMessage::can_merge_with(sptr<Message>) const {
	return false;
}

void NoLocationMessage::merge_with(sptr<Message>) {
	assert(false);
}

bool NoLocationMessage::operator<(const Message& other) const {
	if (typeid(other) == typeid(NoLocation))
		return code() < other.code();
	
	return true;
}

ostream& NoLocationMessage::send_to_stream(ostream& out) const {
	out << to_string(the(MessageInfo).severity(code()))
	    << ": " << message() << "\n";
	return out;
}

////////////////////////////////////////////////////////////////////////////////

string SingleLocationMessage::message() const {
	format result(the(MessageInfo).message(code()));
	foreach (string p, params)
		result % p;
	return result.str();
}

bool SingleLocationMessage::can_merge_with(sptr<Message> other) const {
	return false;
}

void SingleLocationMessage::merge_with(sptr<Message> other) {
	assert(false);
}

bool SingleLocationMessage::operator<(const Message& other) const {
	if (typeid(other) == typeid(NoLocationMessage))
		return true;
	
	if (typeid(other) == typeid(SingleLocationMessage)) {
		const SingleLocationMessage& slother = dynamic_cast<const SingleLocationMessage&>(other);
		return make_tuple(        location,         code())
		     < make_tuple(slother.location, slother.code());
	}
	
	if (typeid(other) == typeid(MultipleLocationMessage)) {
		const MultipleLocationMessage& mlother = dynamic_cast<const MultipleLocationMessage&>(other);
		return make_tuple(         location         ,         code())
		     < make_tuple(*mlother.locations.begin(), mlother.code());
	}
	
	assert(typeid(other) == typeid(CompoundLocationMessage));
	const CompoundLocationMessage& clother = dynamic_cast<const CompoundLocationMessage&>(other);
	return make_tuple(        location,         code())
	     < make_tuple(clother.location, clother.code());
}

ostream& SingleLocationMessage::send_to_stream(ostream& out) const {
	out << to_string(the(MessageInfo).severity(code())) << ": " << message()
	    << "\n    " << location << "\n";
	return out;
}

////////////////////////////////////////////////////////////////////////////////

string MultipleLocationMessage::message() const {
	format result(the(MessageInfo).message(code()));
	foreach (string p, params)
		result % p;
	return result.str();
}

bool MultipleLocationMessage::can_merge_with(sptr<Message> other) const {
	if (typeid(*other) != typeid(MultipleLocationMessage))
		return false;
	
	sptr<MultipleLocationMessage> mlother = other.cast<MultipleLocationMessage>();
	
	return (code() == mlother->code() && params == mlother->params);
}

void MultipleLocationMessage::merge_with(sptr<Message> other) {
	assert(can_merge_with(other));
	
	sptr<MultipleLocationMessage> mlother = other.cast<MultipleLocationMessage>();
	
	locations.insert(*mlother->locations.begin());
}

bool MultipleLocationMessage::operator<(const Message& other) const {
	if (typeid(other) == typeid(NoLocationMessage))
		return true;
	
	if (typeid(other) == typeid(SingleLocationMessage)) {
		const SingleLocationMessage& slother = dynamic_cast<const SingleLocationMessage&>(other);
		return make_tuple(       *locations.begin(),         code())
		     < make_tuple(slother.location         , slother.code());
	}
	
	if (typeid(other) == typeid(MultipleLocationMessage)) {
		const MultipleLocationMessage& mlother = dynamic_cast<const MultipleLocationMessage&>(other);
		
		set<Location>::const_iterator i = locations.begin(),
		                              j = mlother.locations.begin();
		
		while (i != locations.end() && j != mlother.locations.end()) {
			if (*i < *j)
				return true;
			if (*j < *i)
				return false;
		}
		
		return code() < mlother.code();
	}
	
	assert(typeid(other) == typeid(CompoundLocationMessage));
	const CompoundLocationMessage& clother = dynamic_cast<const CompoundLocationMessage&>(other);
	return make_tuple(       *locations.begin(),         code())
	     < make_tuple(clother.location         , clother.code());
}

ostream& MultipleLocationMessage::send_to_stream(ostream& out) const {
	out << to_string(the(MessageInfo).severity(code())) << ": " << message() << "\n";
	foreach(Location l, locations)
		out << "    " << l << "\n";
	return out;
}

////////////////////////////////////////////////////////////////////////////////

string CompoundLocationMessage::message() const {
	format result(the(MessageInfo).message(code()));
	foreach (string p, params)
		result % p;
	return result.str();
}

bool CompoundLocationMessage::can_merge_with(sptr<Message> other) const {
	if (typeid(*other) != typeid(CompoundLocationMessage))
		return false;
	
	sptr<CompoundLocationMessage> clother = other.cast<CompoundLocationMessage>();
	
	return (code() == clother->code() && location.end.successor() == clother->location.begin);
}

void CompoundLocationMessage::merge_with(sptr<Message> other) {
	assert(can_merge_with(other));
	
	sptr<CompoundLocationMessage> clother = other.cast<CompoundLocationMessage>();
	
	location.end = clother->location.end;
	params[0] += clother->params[0];
}

bool CompoundLocationMessage::operator<(const Message& other) const {
	if (typeid(other) == typeid(NoLocationMessage))
		return true;
	
	if (typeid(other) == typeid(SingleLocationMessage)) {
		const SingleLocationMessage& slother = dynamic_cast<const SingleLocationMessage&>(other);
		return make_tuple(        location,         code())
		     < make_tuple(slother.location, slother.code());
	}
	
	if (typeid(other) == typeid(MultipleLocationMessage)) {
		const MultipleLocationMessage& mlother = dynamic_cast<const MultipleLocationMessage&>(other);
		return make_tuple(         location         ,         code())
		     < make_tuple(*mlother.locations.begin(), mlother.code());
	}
	
	assert(typeid(other) == typeid(CompoundLocationMessage));
	const CompoundLocationMessage& clother = dynamic_cast<const CompoundLocationMessage&>(other);
	return make_tuple(        location,         code())
	     < make_tuple(clother.location, clother.code());
}

ostream& CompoundLocationMessage::send_to_stream(ostream& out) const {
	out << to_string(the(MessageInfo).severity(code())) << ": " << message()
	    << "\n    " << location << "\n";
	return out;
}
